<?php
/**
 * 美妆镜设备登录
 * Author: lyun
 * Date: 2018/3/27
 * Created by Panxsoft.
 */


namespace App\Http\Controllers\App;

use App\Exceptions\ApiErrCode;
use App\Exceptions\ApiException;
use App\Http\Controllers\ApiController;
use App\Http\Controllers\Wechat\V3_1\BoomGameController;
use App\Http\Middleware\AppAuth;
use App\Http\Requests\App\LoginRequest;
use App\Http\Requests\App\RefreshTokenRequest;
use App\Http\Requests\App\SettingLoginRequest;
use App\Models\Admin\CosmeticMaterial;
use App\Models\Admin\StoreAdmin;
use App\Models\Customer\Customer;
use App\Models\Customer\DeviceAccount;
use App\Models\Customer\Language;
use App\Services\Locale\Language as ServiceLanguage;
use App\Models\OpenWechat\WechatUser;
use App\Models\Panx\Activity;
use App\Models\Panx\Category;
use App\Models\Panx\SkinDetector;
use App\Services\Repositories\Device\DeviceSpecRepository;
use App\Services\Repositories\Device\DeviceAccountRepository;
use App\Services\Utils\Guard;
use App\Services\Utils\RequestParam;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\JWTGuard;

class DeviceAccountController extends ApiController
{
    const ROUTES = [
        'login'         => 'app.device-account.login',
        'setting-login' => 'app.device-account.setting-login',
        'refresh-token' => 'app.device-account.refresh-token',
    ];

    /**
     * Pad 登录美妆镜登录
     * @see AppAuth
     *
     * @test
     *
     * @param LoginRequest $request
     *
     * @return \Illuminate\Http\Response
     * @throws \Exception
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function login(LoginRequest $request)
    {
        // 先设置 app 的语言，可以正常返回报错信息
        $request_lang = ServiceLanguage::getLoginLanguageCode();
        app()->setLocale($request_lang);

        /** @var DeviceAccount $dev_acnt */
        $dev_acnt = DeviceAccount::findByAccount($request->account);
            $this->checkIsTrue($dev_acnt, trans('app.device_account_error'));
            $this->checkDeviceRelateStatus($dev_acnt);

        $token = $this->tryGenerateLoginToken($request);
        $ttl = Auth::guard(Guard::APP)->factory()->getTTL();
        $device_spec = DeviceSpecRepository::findByCodeOrDefault($request->device_spec_code);

        $request_data = RequestParam::normalize(['longitude', 'latitude'], ['language' => $request_lang]);
        (new DeviceAccountRepository)->updateLoginInfo($dev_acnt, $request_data, $token);

        $account_relate_data = $this->assembleLoginResData($dev_acnt, $request_lang);

        return $this->response(
            compact('token', 'ttl', 'device_spec')
            + $account_relate_data
        );
    }

    /**
     *  app端 店铺设置页面的登录
     * @test
     *
     * @param SettingLoginRequest $request
     *
     * @return array|\Illuminate\Http\Response
     * @throws \Exception
     */
    public function settingLogin(SettingLoginRequest $request)
    {
        $device_account = Guard::deviceAccount();
        
        $this->checkCanOperate(
            $device_account->account === $request->account,
            trans('app.device_account_error')
        );
        
        $this->checkCanOperate(
            password_verify($request->password, $device_account->password),
            trans('app.password_error')
        );

        return $this->response(['check' => true]);
    }

    /**
     * @return Collection|Activity[]
     */
    private function getActivityData(): Collection
    {
        $promos = (new Activity)
            ->filterLimitDate()
            ->latest()
            ->get();

        return $promos;
    }

    /**
     * @param LoginRequest|Request $request
     *
     * @return string
     * @throws \Exception
     */
    private function tryGenerateLoginToken(Request $request): string
    {
        try {
            /**
             * 不能用 credentials ，JWT 0.5.* 不能实现 multi auth
             * attempt to verify the credentials and create a token for the user
             * @see https://github.com/tymondesigns/jwt-auth/issues/513#issuecomment-186087297
             */
            $token = Auth::guard(Guard::APP)->attempt([
                'account'  => $request->account,
                'password' => $request->password
            ]);
            $this->passwordVerifyCheck($token);
        } catch (JWTException $e) {
            if (config('app.debug')) throw $e;

            throw New ApiException(ApiErrCode::TOKEN_GENERATE_FAIL, trans('app.token_generate_fail'));
        }

        return $token;
    }

    /**
     * 检测机器账号、店铺、商家状态是否正常
     *
     * @param DeviceAccount $deviceAccount
     * @throws \Exception
     */
    private function checkDeviceRelateStatus(DeviceAccount $deviceAccount)
    {
        $this->checkIsTrue($deviceAccount->enabled, trans('app.device_account_status'));

        $customer = $deviceAccount->customer;
        $this->checkItemExist($customer, trans('app.customer_error'));
        $this->checkUnless($customer->is_deleted, trans('app.store_disabled'));

        $store = $deviceAccount->store;
        $this->checkItemExist($store, trans('app.store_error'));
        $this->checkUnless($store->is_deleted, trans('app.customer_disabled'));
    }

    /**
     * @param DeviceAccount $admin
     * @return \Illuminate\Support\Collection
     */
    private function getStoreStaff(DeviceAccount $admin)
    {
        $store = $admin->store;

        $store_staffs = $store->storeAdmins()
            ->role(StoreAdmin::STAFF) // 不要改这个，设备只能显示店员
            ->where('is_deleted', StoreAdmin::IS_NOT_DELETED)
            ->whereHas('wechatUser', function (Builder $query) {
                $query->where('subscribe', WechatUser::SUBSCRIBED);
            })
            ->with('wechatUser')
            ->latest()
            ->get();

        return $store_staffs;
    }

    /**
     * @param LoginRequest $request
     * @return string
     */
    private function generateDeviceUuid(LoginRequest $request): string
    {
        if (!$request->mac &&
            !$request->imei) return null;

        return md5(($request->imei) . '#' . ($request->mac));
    }

    /**
     * @param DeviceAccount $account
     * @return bool
     */
    private function shouldPassUuidBindCheck(DeviceAccount $account): bool
    {
        return $account->customer->provider_type === Customer::PROVIDER_TYPE_SOFTWARE
            || $account->is_debug
            || config('app.debug');
    }

    /**
     * @param DeviceAccount $admin
     * @param $request_lang
     * @return Collection
     */
    private function transformOperateCategories(DeviceAccount $admin, $request_lang)
    {
        $language_id = Language::mapCodeToId($request_lang);

        /** @var Collection|Category[] $operate_categories */
        $operate_categories = $admin->customer->getAppCategories($language_id);

        $operate_categories = $operate_categories->filter(function ($category) {
            if (isset($category->pivot->priority)) {
                $category->priority = $category->pivot->priority;

                return $category->pivot->is_show;
            } else {
                return false;
            }
        });

        $operate_categories = $operate_categories->sortByDesc('priority')->values();

        return $operate_categories;
    }

    /**
     * @param DeviceAccount $admin
     * @param $request_lang
     * @return array
     */
    private function assembleLoginResData(DeviceAccount $admin, $request_lang): array
    {
        $store_staffs = $this->getStoreStaff($admin);

        $operate_categories = $this->transformOperateCategories($admin, $request_lang);

        $appid = $admin->customer->authorizer->authorizer_appid ?? null;

        $has_hair = $admin->customer->has_hair ?? null;

        $customer_id = $admin->customer->id;

        $customer_water_mark = $admin->customer->water_mark;

        $skin_detector_ids = $admin->skinDetectors()
            ->pluck((new SkinDetector)->qualifyColumn('id'))
            ->all();
        /** @deprecated (化妆品材质已经去除了) */
        $cosmetic_materials = CosmeticMaterial::filterCustomer($customer_id)
            ->oldest('sort')
            ->get(['id', 'name', 'sort']);

        $admin->addHidden('customer', 'store');

        // 返回当前活动的信息
        $activities = $this->getActivityData();

        // 返回客户的类型(1：开启试妆/开启测肤。2：开启试妆/关闭测肤。3：关闭试妆/开启测肤)
        $customer_type = $this->getCustomerType($customer_id);
        // 客户是否是新鸿基 (废弃了！app本地定义了新鸿基customer_id)
//        $customer_is_sun_hung_kai = ($customer_id === Customer::ID_SUN_HUNG_KAI) ? true : false;
        $game_url = url(BoomGameController::GAME_PATH . '?' . http_build_query(['customer_id' => $customer_id]));

        return compact(
            'store_staffs', 'operate_categories',
            'appid', 'has_hair', 'customer_id', 'admin',
            'customer_water_mark', 'skin_detector_ids', 'activities',
            'cosmetic_materials', 'customer_type', 'game_url'
        );
    }

    /**
     * @test
     *
     * 刷新 token
     *
     * @param RefreshTokenRequest $request - 因为刷新时 token 可能已失效，跳过了中间件，要用 request 检查 token 字段
     *
     * @throws \Exception
     * @return array|\Illuminate\Http\Response
     */
    public function refreshToken(RefreshTokenRequest $request)
    {
        /** @var JWTGuard $jwt */
        $jwt = Auth::guard(Guard::APP);

        try {
            $token = $jwt->refresh();
        } catch (\Exception $e) {
            $token = null;
            $this->checkFailed($e->getMessage());
        }

        $jwt->setToken($token);
        /**
         * @var DeviceAccount $account
         *
         * 这里不先 set 为什么使用 postman 会报错，找不到 user 呢？ 测试类却没报错？
         */
        $account = $jwt->user();
            $this->checkItemExist($account, trans('app.device_account_error'));

        $account->refreshRememberToken($token);

        return $this->response(compact('token'));
    }

    /**
     * @param $customer_id
     * @return int
     */
    private function getCustomerType($customer_id)
    {
        // 1：开启试妆/开启测肤。2：开启试妆/关闭测肤。3：关闭试妆/开启测肤
        if (in_array($customer_id, Customer::SPECIFY_CUSTOMER_IDS)) {
            $customer_type = Customer::CUSTOMER_TYPE_MAKEUP;
        } else {
            $customer_type = Customer::CUSTOMER_TYPE_ALL;
        }

        return $customer_type;
    }
}
